// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at 
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. 
// ========================================================================

package org.eclipse.jetty.util;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/* ------------------------------------------------------------ */
/**
 * A multi valued Map. This Map specializes HashMap and provides methods that operate on multi valued items.
 * <P>
 * Implemented as a map of LazyList values
 * 
 * @param <K> The key type of the map.
 * @see LazyList
 */
@SuppressWarnings({ "rawtypes" })
public class MultiMap<K> implements ConcurrentMap<K, Object>, Serializable
{

	private static final long serialVersionUID = -6878723138353851005L;
	Map<K, Object> _map;
	ConcurrentMap<K, Object> _cmap;

	public MultiMap()
	{
		_map = new HashMap<K, Object>();
	}

	public MultiMap(Map<K, Object> map)
	{
		if (map instanceof ConcurrentMap)
			_map = _cmap = new ConcurrentHashMap<K, Object>(map);
		else
			_map = new HashMap<K, Object>(map);
	}

	public MultiMap(MultiMap<K> map)
	{
		if (map._cmap != null)
			_map = _cmap = new ConcurrentHashMap<K, Object>(map._cmap);
		else
			_map = new HashMap<K, Object>(map._map);
	}

	public MultiMap(int capacity)
	{
		_map = new HashMap<K, Object>(capacity);
	}

	public MultiMap(boolean concurrent)
	{
		if (concurrent)
			_map = _cmap = new ConcurrentHashMap<K, Object>();
		else
			_map = new HashMap<K, Object>();
	}

	/* ------------------------------------------------------------ */
	/**
	 * Get multiple values. Single valued entries are converted to singleton lists.
	 * 
	 * @param name The entry key.
	 * @return Unmodifieable List of values.
	 */
	public List getValues(Object name)
	{
		return LazyList.getList(_map.get(name), true);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Get a value from a multiple value. If the value is not a multivalue, then index 0 retrieves the value or null.
	 * 
	 * @param name The entry key.
	 * @param i Index of element to get.
	 * @return Unmodifieable List of values.
	 */
	public Object getValue(Object name, int i)
	{
		Object l = _map.get(name);
		if (i == 0 && LazyList.size(l) == 0)
			return null;
		return LazyList.get(l, i);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Get value as String. Single valued items are converted to a String with the toString() Object method. Multi valued entries are converted to a comma separated List. No quoting of commas within values is performed.
	 * 
	 * @param name The entry key.
	 * @return String value.
	 */
	public String getString(Object name)
	{
		Object l = _map.get(name);
		switch (LazyList.size(l))
		{
			case 0:
				return null;
			case 1:
				Object o = LazyList.get(l, 0);
				return o == null ? null : o.toString();
			default: {
				StringBuilder values = new StringBuilder(128);
				for (int i = 0; i < LazyList.size(l); i++)
				{
					Object e = LazyList.get(l, i);
					if (e != null)
					{
						if (values.length() > 0)
							values.append(',');
						values.append(e.toString());
					}
				}
				return values.toString();
			}
		}
	}

	/* ------------------------------------------------------------ */
	public Object get(Object name)
	{
		Object l = _map.get(name);
		switch (LazyList.size(l))
		{
			case 0:
				return null;
			case 1:
				Object o = LazyList.get(l, 0);
				return o;
			default:
				return LazyList.getList(l, true);
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * Put and entry into the map.
	 * 
	 * @param name The entry key.
	 * @param value The entry value.
	 * @return The previous value or null.
	 */
	public Object put(K name, Object value)
	{
		return _map.put(name, LazyList.add(null, value));
	}

	/* ------------------------------------------------------------ */
	/**
	 * Put multi valued entry.
	 * 
	 * @param name The entry key.
	 * @param values The List of multiple values.
	 * @return The previous value or null.
	 */
	public Object putValues(K name, List<? extends Object> values)
	{
		return _map.put(name, values);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Put multi valued entry.
	 * 
	 * @param name The entry key.
	 * @param values The String array of multiple values.
	 * @return The previous value or null.
	 */
	public Object putValues(K name, String... values)
	{
		Object list = null;
		for (int i = 0; i < values.length; i++)
			list = LazyList.add(list, values[i]);
		return _map.put(name, list);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Add value to multi valued entry. If the entry is single valued, it is converted to the first value of a multi valued entry.
	 * 
	 * @param name The entry key.
	 * @param value The entry value.
	 */
	public void add(K name, Object value)
	{
		Object lo = _map.get(name);
		Object ln = LazyList.add(lo, value);
		if (lo != ln)
			_map.put(name, ln);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Add values to multi valued entry. If the entry is single valued, it is converted to the first value of a multi valued entry.
	 * 
	 * @param name The entry key.
	 * @param values The List of multiple values.
	 */
	public void addValues(K name, List<? extends Object> values)
	{
		Object lo = _map.get(name);
		Object ln = LazyList.addCollection(lo, values);
		if (lo != ln)
			_map.put(name, ln);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Add values to multi valued entry. If the entry is single valued, it is converted to the first value of a multi valued entry.
	 * 
	 * @param name The entry key.
	 * @param values The String array of multiple values.
	 */
	public void addValues(K name, String[] values)
	{
		Object lo = _map.get(name);
		Object ln = LazyList.addCollection(lo, Arrays.asList(values));
		if (lo != ln)
			_map.put(name, ln);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Remove value.
	 * 
	 * @param name The entry key.
	 * @param value The entry value.
	 * @return true if it was removed.
	 */
	public boolean removeValue(K name, Object value)
	{
		Object lo = _map.get(name);
		Object ln = lo;
		int s = LazyList.size(lo);
		if (s > 0)
		{
			ln = LazyList.remove(lo, value);
			if (ln == null)
				_map.remove(name);
			else
				_map.put(name, ln);
		}
		return LazyList.size(ln) != s;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Put all contents of map.
	 * 
	 * @param m Map
	 */
	public void putAll(Map<? extends K, ? extends Object> m)
	{
		boolean multi = (m instanceof MultiMap);

		if (multi)
		{
			for (Map.Entry<? extends K, ? extends Object> entry: m.entrySet())
			{
				_map.put(entry.getKey(), LazyList.clone(entry.getValue()));
			}
		}
		else
		{
			_map.putAll(m);
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Map of String arrays
	 */
	public Map<K, String[]> toStringArrayMap()
	{
		HashMap<K, String[]> map = new HashMap<K, String[]>(_map.size() * 3 / 2);

		for (Map.Entry<K, Object> entry: _map.entrySet())
		{
			String[] a = LazyList.toStringArray(entry.getValue());
			map.put(entry.getKey(), a);
		}
		return map;
	}

	@Override
	public String toString()
	{
		return _cmap == null ? _map.toString() : _cmap.toString();
	}

	public void clear()
	{
		_map.clear();
	}

	public boolean containsKey(Object key)
	{
		return _map.containsKey(key);
	}

	public boolean containsValue(Object value)
	{
		return _map.containsValue(value);
	}

	public Set<Entry<K, Object>> entrySet()
	{
		return _map.entrySet();
	}

	@Override
	public boolean equals(Object o)
	{
		return _map.equals(o);
	}

	@Override
	public int hashCode()
	{
		return _map.hashCode();
	}

	public boolean isEmpty()
	{
		return _map.isEmpty();
	}

	public Set<K> keySet()
	{
		return _map.keySet();
	}

	public Object remove(Object key)
	{
		return _map.remove(key);
	}

	public int size()
	{
		return _map.size();
	}

	public Collection<Object> values()
	{
		return _map.values();
	}

	public Object putIfAbsent(K key, Object value)
	{
		if (_cmap == null)
			throw new UnsupportedOperationException();
		return _cmap.putIfAbsent(key, value);
	}

	public boolean remove(Object key, Object value)
	{
		if (_cmap == null)
			throw new UnsupportedOperationException();
		return _cmap.remove(key, value);
	}

	public boolean replace(K key, Object oldValue, Object newValue)
	{
		if (_cmap == null)
			throw new UnsupportedOperationException();
		return _cmap.replace(key, oldValue, newValue);
	}

	public Object replace(K key, Object value)
	{
		if (_cmap == null)
			throw new UnsupportedOperationException();
		return _cmap.replace(key, value);
	}
}
